package com.yance.fim.handler;

import com.yance.fim.core.enums.Code;
import com.yance.fim.core.packets.FimPacket;
import com.yance.fim.handler.service.Handler;
import com.yance.fim.handler.service.IMAckCallback;
import com.yance.fim.handler.service.IMHandler;
import com.yance.fim.utils.ClassUtil;
import com.yance.fim.utils.Constants;
import com.yance.fim.utils.ThreadPoolExecutorWrapper;
import com.yance.fim.utils.Utility;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.tio.core.ChannelContext;

import java.util.HashMap;
import java.util.concurrent.Executors;

import static com.yance.fim.core.enums.ResponseCode.ERROR_CODE_NOT_IMPLEMENT;

@Service
public class PushMessageHandler {

    Logger LOG = LoggerFactory.getLogger(PushMessageHandler.class);

    private HashMap<String, IMHandler> imHandlers = new HashMap<>();

    private final ThreadPoolExecutorWrapper threadPoolExecutorWrapper;


    public PushMessageHandler() {
        int threadNum = Runtime.getRuntime().availableProcessors() * 2;
        this.threadPoolExecutorWrapper = new ThreadPoolExecutorWrapper(Executors.newScheduledThreadPool(threadNum), threadNum, "business");
        IMHandler.init(threadPoolExecutorWrapper);
        registerAllAction();
    }


    public void receiveSendMessage(FimPacket fimPacket, ChannelContext channelContext) {
        Code code = fimPacket.getHeader().getCode();
        String codeName = code.name();
        String clientID = (String) channelContext.getAttribute(Constants.ATTR_CLIENTID);
        String fromUser = (String) channelContext.getAttribute(Constants.ATTR_USERACCOUNT);
        int messageID = fimPacket.getHeader().getMessageId();
        IMAckCallback imAckCallback = (errorCode, ackPayload) -> {
            LOG.info("handle message errorcode {}", errorCode);
            sendPushAck(clientID, messageID, ackPayload, code);
        };
        byte[] payloadContent = fimPacket.getBody();
        IMHandler handler = imHandlers.get(codeName);
        if (handler != null) {
            handler.doHandler(clientID, fromUser, codeName, payloadContent, imAckCallback);
        } else {
            LOG.error("imHandler unknown topic={}", codeName);
            ByteBuf ackPayload = Unpooled.buffer();
            ackPayload.ensureWritable(1).writeByte(ERROR_CODE_NOT_IMPLEMENT.getCode());
            try {
                imAckCallback.onIMHandled(ERROR_CODE_NOT_IMPLEMENT, ackPayload);
            } catch (Exception e) {
                Utility.printExecption(LOG, e);
            }
        }
    }

    /**
     * puback 消息格式
     * 第一个自己消息错误码，之后跟着消息体
     */
    private void sendPushAck(String clientId, int messageID, ByteBuf payload, Code subSignal) {
//        LOG.info("clientId {} messagId {} send PUB_ACK message size {} subSignal {}", clientId, messageID, payload.readableBytes(), subSignal);
//        PublishAckMessagePacket publishAckMessagePacket = new PublishAckMessagePacket();
//        publishAckMessagePacket.setMessageId(messageID);
//        publishAckMessagePacket.setSubSignal(subSignal);
//        byte[] messageByte = new byte[payload.readableBytes()];
//        payload.readBytes(messageByte);
//        publishAckMessagePacket.setBody(messageByte);
//        //增加websocket发送
//        boolean flag = Tio.sendToBsId(ShowcaseWebsocketStarter.serverGroupContext, clientId, publishAckMessagePacket);
//        LOG.info("send websocket client {} message size {} {}", clientId, messageByte.length, flag ? "sucess" : "fail");
    }


    private void registerAllAction() {
        try {
            for (Class cls : ClassUtil.getAllAssignedClass(IMHandler.class)) {
                Handler annotation = (Handler) cls.getAnnotation(Handler.class);
                if (annotation != null) {
                    IMHandler handler = (IMHandler) cls.newInstance();
                    imHandlers.put(annotation.value(), handler);
                }
            }
        } catch (Exception e) {
            Utility.printExecption(LOG, e);
        }
    }
}
